package kr.ac.ssu.ailab.Control;

import java.awt.Point;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import kr.ac.ssu.ailab.Model.Data.Cell;
import kr.ac.ssu.ailab.Model.Data.Edge;
import kr.ac.ssu.ailab.Model.Data.Cluster;
import kr.ac.ssu.ailab.View.SeqTest;

/**
 * Graph를 Control하는 메소드
 * 
 * @author Rivers
 * 
 */
public class GraphControl
{
	public static final int NONE = -2;
	public static final int FOUND = 1;
	public static final int PartialPATH = 2;
	public static final int NOT_FOUND = 0;
	private static GraphControl graphControl = null;
	private ClusterControl CCInstance = ClusterControl.getInstance();
	private transient Map<Cell, ArrayList<Cell>> graph = null;
	private transient Map<String, Double> graphCost = null;
	private transient Map<Cell, ArrayList<Cell>> graphLv2 = null;
	private transient Map<String, Double> graphCostLv2 = null;
	private transient Vector<Cell> goalList = null;
	private Vector<Cell> openList = null;
	private Vector<Cell> closedList = null;
	private boolean needAbstract = false;
	private double lv2Cost;
	private Cell start = null;
	private Cell finish = null;

	/**
	 * 생성자
	 */
	private GraphControl()
	{
		graph = new HashMap<Cell, ArrayList<Cell>>();
		graphCost = new HashMap<String, Double>();
		graphLv2 = new HashMap<Cell, ArrayList<Cell>>();
		graphCostLv2 = new HashMap<String, Double>();
		goalList = new Vector<Cell>();

		this.openList = null;
		this.closedList = null;

		this.needAbstract = false;
		this.lv2Cost = 0;

		this.start = null;
		this.finish = null;

	}

	/**
	 * 그래프 컨트롤을 reset 하는 메소드
	 */
	public void reset()
	{
		if (graphControl != null)
			graphControl = new GraphControl();
	}

	/**
	 * GraphControl 의 싱글톤 유지를 위해서 생성한 메소드
	 * 
	 * @return
	 */
	public static GraphControl getInstance()
	{
		if (graphControl == null)
			graphControl = new GraphControl();
		return graphControl;
	}

	/**
	 * GraphControl Instance 의 초기화
	 * 
	 * @return
	 */
	public static void initInstance()
	{
		if (graphControl != null)
			graphControl = null;
	}

	/**
	 * Lv1 Graph 의 EdgeCost 를 add 하는 메소드
	 * 
	 * @param start
	 * @param finish
	 * @param cost
	 */
	public void addEdgeCost(Cell start, Cell finish, double cost)
	{
		Edge edge = new Edge(start.getPosition(), finish.getPosition());
		Edge reverseEdge = new Edge(finish.getPosition(), start.getPosition());
		graphCost.put(edge.toString(), cost);
		graphCost.put(reverseEdge.toString(), cost);
	}

	/**
	 * Lv1 Graph 의 EdgeCost 를 remove 하는 메소드
	 * 
	 * @param start
	 * @param finish
	 */
	public void removeEdgeCost(Cell start, Cell finish)
	{
		Edge edge = new Edge(start.getPosition(), finish.getPosition());
		Edge reverseEdge = new Edge(finish.getPosition(), start.getPosition());
		graphCost.remove(edge.toString());
		graphCost.remove(reverseEdge.toString());
	}

	/**
	 * Lv1 Graph 의 Edge 를 add 하는 메소드
	 * 
	 * @param start
	 * @param finish
	 */
	public void addEdge(Cell start, Cell finish)
	{
		ArrayList<Cell> temp = new ArrayList<Cell>();
		ArrayList<Cell> temp2 = new ArrayList<Cell>();

		if (isContain(start))
		{
			temp.addAll(graph.get(start));
		}
		if (isContain(finish))
		{
			temp2.addAll(graph.get(finish));
		}
		if (!temp.contains(finish))
		{
			temp.add(finish);
			graph.put(start, temp);
		}
		if (!temp2.contains(start))
		{
			temp2.add(start);
			graph.put(finish, temp2);
		}

	}

	/**
	 * Lv1 Graph 의 Edge를 Remove 하는 메소드
	 * 
	 * @param start
	 * @param finish
	 */
	public void removeEdgeLv1(Cell start, Cell finish)
	{
		if (graph.containsKey(start))
		{
			graph.remove(start);
		}
		if (graph.containsValue(finish))
		{
			ArrayList temp = graph.get(finish);
			temp.remove(start);
			graph.put(finish, temp);
		}

	}

	/**
	 * Graph �� ���� Parent �� �ʱ�ȭ ���ִ� method
	 */
	public void clearGraphParent()
	{
		if (graph != null)
		{
			Cell temp = null;
			Set<Cell> key = graph.keySet();
			for (Iterator i = key.iterator(); i.hasNext();)
			{
				temp = (Cell) i.next();
				temp.setGraphParent(null);
				ArrayList<Cell> tempList = graph.get(temp);
				for (int j = 0; j < tempList.size(); j++)
				{
					tempList.get(j).setGraphParent(null);
				}
			}
		}
		if (graphLv2 == null)
		{
			Cell temp = null;
			Set<Cell> key = graphLv2.keySet();
			for (Iterator i = key.iterator(); i.hasNext();)
			{
				temp = (Cell) i.next();
				temp.setGraphParent(null);
				ArrayList<Cell> tempList = graphLv2.get(temp);
				for (int j = 0; j < tempList.size(); j++)
				{
					tempList.get(j).setGraphParent(null);
				}
			}

		}
	}

	/**
	 * Lv2 Graph �� Edge �� get �ϴ� method
	 * 
	 * @param start
	 * @return
	 */
	public ArrayList<Cell> getEdgeLv2(Cell start)
	{
		return graphLv2.get(start);
	}

	/**
	 * Lv1 Graph �� Edge �� get �ϴ� method
	 * 
	 * @param start
	 * @return
	 */
	public ArrayList<Cell> getEdge(Cell start)
	{
		return graph.get(start);
	}

	/**
	 * Lv2 Graph �� Node �� �ִ��� ����� Ȯ���ϴ� method
	 * 
	 * @param key
	 * @return
	 */
	public boolean isContainLv2(Cell key)
	{
		return graphLv2.containsKey(key);
	}

	/**
	 * Lv2 Graph �� Node �� �ִ��� ����� Ȯ���ϴ� method
	 * 
	 * @param key
	 * @return
	 */
	public boolean isContain(Cell key)
	{
		return graph.containsKey(key);
	}

	/**
	 * 구성되어진 Lv1 Graph를 기반으로 중간 목표 이동점을 찾는 메소드
	 * 
	 * @param abstracted
	 */
	public void findGraph()
	{

		int found = NOT_FOUND;
		goalList = new Vector<Cell>();
		openList = new Vector<Cell>();
		closedList = new Vector<Cell>();

		start = SeqTest.mapControl.getStartCell();
		finish = SeqTest.mapControl.getFinishCell();

		openList.add(start);

		while (found == NOT_FOUND)
		{

			found = step();

			if (found == FOUND)
			{
				setGraphPath();
			}
		}

	}

	/**
	 * Search 중에 Event 가 발생해서 다시 중간 목표 이동점을 찾는 메소드
	 * 
	 * @param abstracted
	 */
	public void reFindGraph(Cell startCell)
	{

		int found = NOT_FOUND;
		goalList = new Vector<Cell>();
		openList = new Vector<Cell>();
		closedList = new Vector<Cell>();

		start = startCell;
		finish = SeqTest.mapControl.getFinishCell();

		openList.add(start);

		while (found == NOT_FOUND)
		{

			found = step();

			if (found == FOUND)
			{
				setGraphPath();
			}
		}

	}

	/**
	 * Lv1 Graph 에서 Graph Path 를 set 하는 메소ㄷ
	 */
	private void setGraphPath()
	{
		Cell next = null;
		Cell stop = SeqTest.mapControl.getStartCell();
		Cell now = SeqTest.mapControl.getFinishCell();

		boolean finish = false;

		while (!finish)
		{
			goalList.add(now);
			next = SeqTest.mapControl.getCellArray()[now.getGraphParent().x][now.getGraphParent().y];
			next.setType(CellControl.PART_OF_PATH);

			now = next;
			if (now == stop)
			{
				finish = true;
				break;
			}
		}
	}

	/**
	 * Lv1 Graph 에서 부분 경로를 찾기 위해서 실행하는 메소드
	 * 
	 * @return
	 */
	private int step()
	{
		Vector<Cell> temp = (Vector<Cell>) openList.clone();

		int result = NOT_FOUND;
		Cell[] next = null;
		Cell best = null;
		Cell now = null;

		try
		{
			best = temp.elementAt(temp.size() - 1);
		}
		catch (ArrayIndexOutOfBoundsException e)
		{
			// System.err.println("NODE is Block");
		}

		double min = Double.MAX_VALUE;
		double score = 0;

		for (int i = 0; i < temp.size(); i++)
		{
			now = (Cell) temp.elementAt(i);
			double heuristic = HeuristicPathFinding.tie_breaking_Heuristic(now, finish, 1.0);
			score = now.getFromGraphWeight() + heuristic;

			if (score < min)
			{
				min = score;
				best = now;
			}
		}
		now = best;

		openList.remove(now);
		closedList.add(now);

		// �ش� Ŭ�������� edgeCost�� ������ �´�.
		try
		{
			next = getAdjacentEdge(now);

		}
		catch (NullPointerException e)
		{
			// System.out.println("NOW NODE IS BLOCK2");
			result = NONE;
		}
		if (next != null)
			for (int i = 0; i < next.length; i++)
			{
				if (next[i] == finish)
				{
					result = FOUND;
				}
				if (next[i] != null)
				{
					Point nextP = next[i].getPosition();
					next[i].setGraphParent(now.getPosition());
					Edge edge = new Edge(now.getPosition(), nextP);
					double gCost = now.getFromGraphWeight() + graphCost.get(edge.toString())
							+ now.getWeight();

					next[i].setFromNowGraphWeight(gCost);

					if (!openList.contains(next[i]) && !closedList.contains(next[i]))
					{
						// next[i].setType(CellControl.TRANS_CELL);
						// next[i].repaint();
						openList.addElement(next[i]);
					}
				}
			}

		return result;

	}

	/**
	 * Lv1 Graph 에서 근접한 Edge 를 get 하는 메소드
	 * 
	 * @param now
	 * @return
	 */
	public Cell[] getAdjacentEdge(Cell now)
	{

		Cell[] next = null;
		ArrayList<Cell> adjacentEdge = getEdge(now);
		int size = adjacentEdge.size();

		Cell[] edge = new Cell[size];

		for (int i = 0; i < size; i++)
		{
			edge[i] = adjacentEdge.get(i);
		}

		return edge;
	}

	/**
	 * GoalList 를 null 로 set 하는 메소드
	 * 
	 * @return
	 */
	public Vector<Cell> setNuLLGoalList()
	{
		goalList = new Vector<Cell>();
		return goalList;
	}

	/**
	 * GoalList 를 get 하는 메소드
	 * 
	 * @return
	 */
	public Vector<Cell> getGoalList()
	{
		return goalList;
	}

	/**
	 * GoalList 의 subGoal 을 add 하는 method
	 * 
	 * @param c
	 */
	public void addGoalList(Cell c)
	{
		this.goalList.add(c);
	}

	/**
	 * Start 와 Finish 의 포인트를 그래프와 연결하는 메소드
	 * 
	 */
	public void connectSFGraph()
	{
		boolean found = false;
		ClusterControl instanceCC = null;

		Cell start = SeqTest.mapControl.getStartCell();
		ArrayList<Cell> startConnect = new ArrayList<Cell>();
		startConnect = CCInstance.getStartCluster().getStandCell();
		Cell finish = SeqTest.mapControl.getFinishCell();
		ArrayList<Cell> finishConnect = new ArrayList<Cell>();
		finishConnect = CCInstance.getFinishCluster().getStandCell();

		for (int i = 0; i < startConnect.size(); i++)
		{
			SearchModule pSearch = new SearchModule(start, startConnect.get(i));

			if (!pSearch.StartFinishAddSearch())
			{

				Cluster next[] = CCInstance.getAdjacentCluster(CCInstance.getStartCluster());

				for (int k = 0; k < next.length; k++)
				{

					pSearch = new SearchModule(start, next[k].getStandCell().get(i));
					if (pSearch.StartFinishAddSearch())
					{
						found = true;
						addEdge(start, next[k].getStandCell().get(i));
						addEdgeCost(start, next[k].getStandCell().get(i), pSearch.getResult_score());

					}
				}

				if (!found)
				{
					pSearch = new SearchModule(start, finish);
					if (pSearch.StartFinishAddSearch())
						System.err.println("SEARCH");
				}
			}
			else
			{
				addEdge(start, startConnect.get(i));
				addEdgeCost(start, startConnect.get(i), pSearch.getResult_score());

			}
			found = false;

		}

		for (int i = 0; i < finishConnect.size(); i++)
		{
			SearchModule pSearch = new SearchModule(finish, finishConnect.get(i));
			if (!pSearch.StartFinishAddSearch())
			{

				instanceCC = CCInstance;
				Cluster next[] = instanceCC.getAdjacentCluster(CCInstance.getFinishCluster());
				for (int k = 0; k < next.length; k++)
				{
					pSearch = new SearchModule(start, next[k].getStandCell().get(i));
					if (pSearch.StartFinishAddSearch())
					{
						addEdge(finish, next[k].getStandCell().get(i));
						addEdgeCost(finish, next[k].getStandCell().get(i), pSearch
								.getResult_score());

						break;
					}
				}
			}
			else
			{
				addEdge(finish, finishConnect.get(i));
				addEdgeCost(finish, finishConnect.get(i), pSearch.getResult_score());

			}
		}

	}

	/**
	 * 시작점을 그래프에 연결하는 메소드
	 */
	public void connectSGraph()
	{
		boolean found = false;
		ClusterControl instanceCC = null;

		Cell start = SeqTest.mapControl.getStartCell();
		ArrayList<Cell> startConnect = new ArrayList<Cell>();
		startConnect = CCInstance.getStartCluster().getStandCell();
		Cell finish = SeqTest.mapControl.getFinishCell();
		ArrayList<Cell> finishConnect = new ArrayList<Cell>();
		finishConnect = CCInstance.getFinishCluster().getStandCell();

		for (int i = 0; i < startConnect.size(); i++)
		{
			SearchModule pSearch = new SearchModule(start, startConnect.get(i));

			if (!pSearch.StartFinishAddSearch())
			{

				Cluster next[] = CCInstance.getAdjacentCluster(CCInstance.getStartCluster());

				for (int k = 0; k < next.length; k++)
				{

					pSearch = new SearchModule(start, next[k].getStandCell().get(i));
					if (pSearch.StartFinishAddSearch())
					{
						found = true;
						addEdge(start, next[k].getStandCell().get(i));
						addEdgeCost(start, next[k].getStandCell().get(i), pSearch.getResult_score());

					}
				}

				if (!found)
				{
					pSearch = new SearchModule(start, finish);
					if (pSearch.StartFinishAddSearch())
						System.err.println("SEARCH");
				}
			}
			else
			{
				addEdge(start, startConnect.get(i));
				addEdgeCost(start, startConnect.get(i), pSearch.getResult_score());

			}

		}

	}
}
